Add optional redirect loop protection to AuthenticationService#752
Add optional redirect loop protection to AuthenticationService#752dereuromark merged 3 commits intocakephp:3.xfrom dereuromark:feature/redirect-loop-protection
Conversation
- Add configurable redirect validation to prevent redirect loop attacks - Checks for nested redirects, deep encoding, blocked patterns, and URL length - Disabled by default for backward compatibility (opt-in) - Add comprehensive test coverage (8 new tests) - Add detailed documentation with security considerations - Fixes issue #751 Real-world evidence shows bots creating 6-7 levels of nested redirects, wasting server resources and potentially enabling security exploits. Configuration example: 'redirectValidation' => [ 'enabled' => true, 'maxDepth' => 1, 'maxEncodingLevels' => 1, 'maxLength' => 2000, 'blockedPatterns' => ['#/login#i', '#/logout#i'], ]
There was a problem hiding this comment.
Pull Request Overview
This PR adds optional redirect validation to AuthenticationService::getLoginRedirect() to prevent redirect loop attacks observed in production. The feature is disabled by default for backward compatibility and can be enabled via the redirectValidation configuration option.
- Adds
validateRedirect()protected method with four validation checks (depth, encoding, length, patterns) - Adds comprehensive test coverage with 8 new test cases
- Provides detailed documentation with security considerations and usage examples
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/AuthenticationService.php | Implements the validateRedirect() method and adds redirectValidation configuration with defaults |
| tests/TestCase/AuthenticationServiceTest.php | Adds 8 comprehensive test cases covering disabled validation, nested redirects, encoding levels, blocked patterns, URL length, custom patterns, and query parameters |
| docs/en/redirect-validation.rst | New documentation page explaining the feature, configuration options, validation logic, and security considerations |
| docs/en/index.rst | Adds link to the new redirect-validation documentation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Fix comparison operators: Use >= instead of > for maxDepth and maxEncodingLevels This correctly blocks URLs when they meet or exceed the threshold - Replace empty() with ! for enabled check (cleaner intent) - All tests still pass (312 tests, 926 assertions) Addresses feedback from @ADmad and @Copilot in PR #752
|
Thanks for the quick review @ADmad and @copilot! I've addressed all the feedback in commit 1ae39c2: ✅ Fixed comparison operators (>= instead of >)
✅ Replaced empty() with !
All tests still pass (312 tests, 926 assertions). Ready for another review! |
markstory
left a comment
There was a problem hiding this comment.
This whole thing seems quite complex for the problem it is trying to solve.
Address maintainer feedback from @markstory and @ADmad: - Remove blockedPatterns configuration option - Remove pattern-based validation logic - Update documentation to show custom pattern validation in subclass - Remove 2 pattern-based tests (testGetLoginRedirectValidationBlockedPatterns, testGetLoginRedirectValidationCustomPatterns) Result: Simpler, focused implementation covering the core security issues: - Nested redirect detection - Deep encoding detection - URL length limits Custom pattern blocking can still be achieved by overriding validateRedirect(). All tests pass: 310 tests, 920 assertions Code style checks pass
|
Thanks for the feedback @markstory and @ADmad! I've simplified the implementation in commit 591ec19: Removed❌ blockedPatterns configuration - Removed entirely What Remains (Core Security Features)✅ Nested redirect detection - Blocks Custom Pattern BlockingApplications that need pattern-based blocking (e.g., blocking redirects to Result: Much simpler implementation focused on the core redirect loop vulnerability.
Ready for another review! |
|
Gonna test it on the server before we tag any release. |
|
So far so good. Only one more case where it can run into loops. when working together with authorization - and UnauthorizedHandler. This seems to solve it, but its not really too nice. // SafeRedirectHandler extends plugin one
public function handle(Exception $exception, ServerRequestInterface $request, array $options = []): ResponseInterface {
// If user is already logged in but not authorized, redirect to fallback
// instead of login page (which would cause a loop)
$identity = $request->getAttribute('identity');
if ($identity) {
$message = $options['unauthorizedMessage'] ?? __('You are not authorized to access that location.');
if ($message) {
/** @var \Cake\Http\ServerRequest $request */
$session = $request->getSession();
$session->write('Flash.flash', [
[
'message' => $message,
'key' => 'flash',
'element' => 'flash/error',
'params' => [],
],
]);
}
$fallbackUrl = Router::url(['prefix' => false, 'plugin' => false, 'controller' => 'Account', 'action' => 'index']);
return (new Response())
->withHeader('Location', $fallbackUrl)
->withStatus(302);
} |
Summary
This PR adds optional redirect validation to
AuthenticationService::getLoginRedirect()to prevent redirect loop attacks that have been observed in production environments.Problem
The current implementation validates that redirect URLs are relative (not external) but does NOT check for:
Real-world evidence: Production logs show bots (especially GPTBot) creating 6-7 levels of nested redirects, wasting server resources and potentially enabling security exploits.
Solution
Adds configurable
redirectValidationoption with:validateRedirect()method can be overriddenConfiguration Example
Changes
validateRedirect()protected method toAuthenticationServiceredirectValidationconfiguration with sensible defaultsgetLoginRedirect()to call validationdocs/en/redirect-validation.rst)Validation Logic
redirect=occurrences in decoded URL%25(percent-encoding of %)If validation fails, returns
nullinstead of invalid URL.Security Impact
Before:
/login?redirect=/login?redirect=/login...%252F/logoutcausing loopsAfter (when enabled):
References
Testing
Checklist
Ready for review! Happy to make any adjustments based on feedback.